
using System;
using System.Collections.Generic;
using UnityEngine;

namespace MagicVoxelParse
{
    public class TDataHolder
    {
        private byte[] _bytesArray;
        private int _cursor;

        public readonly TVoxelData data;

        private readonly Stack<byte[]> _arrayArray;
        private readonly Stack<int> _cursorArray;

        #region Debug

        private List<string> unknowChunk = new List<string>();

        #endregion

        public TDataHolder(byte[] unit8Array)
        {
            _bytesArray = unit8Array;
            _cursor = 0;
            data = new TVoxelData();
            _arrayArray = new Stack<byte[]>(0);
            _cursorArray = new Stack<int>(0);
        }

        private byte Next()
        {
            if (_bytesArray.LongLength <= _cursor)
            {
                throw new ArgumentOutOfRangeException(string.Format("下标溢{0}出", _cursor));
            }

            return _bytesArray[_cursor++];
        }

        private bool HasNext()
        {
            return _cursor < _bytesArray.LongLength;
        }

        private void Push(int chunkSize)
        {
            var array = new byte[chunkSize];
            for (var i = 0; i < array.Length; i++)
            {
                array[i] = Next();
            }
            
            _arrayArray.Push(_bytesArray);
            _cursorArray.Push(_cursor);

            _bytesArray = array;
            _cursor = 0;
        }

        private void Pop()
        {
            _bytesArray = _arrayArray.Pop();
            _cursor = _cursorArray.Pop();
        }

        private int ParseInt32()
        {
            var intValue = 0;
            for (var i = 0; i < 4; i++)
            {
                intValue += Next() * (int)Mathf.Pow(256, i);
            }

            //unsigned to signed
            return ~~intValue;
        }

        private string ParseString()
        {
            var n = ParseInt32();
            var str = string.Empty;
            for (var i = 0; i < n; i++)
            {
                str += Convert.ToChar(Next());
            }

            return str;
        }

        private Dictionary<string, string> ParseDict()
        {
            var n = ParseInt32();
            var dict = new Dictionary<string,string>();
            for (var i = 0; i < n; i++)
            {
                var key = ParseString();
                var value = ParseString();
                dict.Add(key,value);
            }

            return dict;
        }

        public byte ParseRotation()
        {
            return Next();
        }

        public static void Init(TDataHolder dataHolder)
        {
            ParseMagicNumber(dataHolder);
            ParseVersionNumber(dataHolder);
            ParseChunk(dataHolder);

            #region Debug
            
            dataHolder.unknowChunk.ForEach(Debug.Log);

            #endregion
        }

        private static void ParseMagicNumber(TDataHolder dataHolder)
        {
            var str = string.Empty;
            for (var i = 0; i < 4; i++)
            {
                str += Convert.ToChar(dataHolder.Next());
            }

            if (str != "VOX ")
            {
                throw new Exception("无效...");
            }
            
            Debug.Log("ParseMagicNumber : " + str);
        }

        private static void ParseVersionNumber(TDataHolder dataHolder)
        {
            var ver = dataHolder.ParseInt32();
            Debug.Log("ParseVersionNumber : " + ver);
        }

        private static void ParseChunk(TDataHolder dataHolder)
        {
            var go = dataHolder.HasNext();
            while (go)
            {
                var chunkId = ParseChunkId(dataHolder);
                var chunkSize = ParseSizeOfChunkContents(dataHolder);
                var chunkChildSize = ParseTotalSizeOfChildrenChunks(dataHolder);
                ParseContents(chunkId, chunkSize, dataHolder);
                go = dataHolder.HasNext();
            }
        }

        private static string ParseChunkId(TDataHolder dataHolder)
        {
            var id = string.Empty;
            for (var i = 0; i < 4; i++)
            {
                id += Convert.ToChar(dataHolder.Next());
            }
            
//            Debug.Log("chunk id = " + id);

            return id;
        }

        private static int ParseSizeOfChunkContents(TDataHolder dataHolder)
        {
            return dataHolder.ParseInt32();
        }

        private static int ParseTotalSizeOfChildrenChunks(TDataHolder dataHolder)
        {
            return dataHolder.ParseInt32();
        }

        private static void ParseContents(string chunkId, int chunkSize, TDataHolder dataHolder)
        {
            dataHolder.Push(chunkSize);

            switch (chunkId) {
                case "PACK":
                    ContentsOfPackChunk(dataHolder);
                    break;
                case "SIZE":
                    ContentsOfSizeChunk(dataHolder);
                    break;
                case "XYZI":
                    ContentsOfVoxelChunk(dataHolder);
                    break;
                case "RGBA":
                    ContentsOfPaletteChunk(dataHolder);
                    break;
                case "MATT":
                    ContentsOfMaterialChunk(dataHolder);
                    break;
                case "nTRN":
                    ContentsOfTransformNodeChunk(dataHolder);
                    break;
                case "nGRP":
                    ContentsOfGroupNodeChunk(dataHolder);
                    break;
                case "nSHP":
                    ContentsOfShapeNodeChunk(dataHolder);
                    break;
                case "MATL":
                    ContentsOfMaterialExChunk(dataHolder);
                    break;
                default:
                    if(!dataHolder.unknowChunk.Contains(chunkId)){dataHolder.unknowChunk.Add(chunkId);}
                    Debug.Log(string.Format("Unknow Chunk = {0},size = {1}",chunkId,chunkSize));
                    break;
            }

            dataHolder.Pop();
        }

        private static void ContentsOfPackChunk(TDataHolder dataHolder)
        {
            var size = dataHolder.ParseInt32();
            Debug.Log("  num of SIZE and XYZI chunks = " + size);
        }

        private static void ContentsOfSizeChunk(TDataHolder dataHolder)
        {
            var x = dataHolder.ParseInt32();
            var y = dataHolder.ParseInt32();
            var z = dataHolder.ParseInt32();
            
            Debug.Log(string.Format("  bounding box size = {0},{1},{2}",x,y,z));
            
            //取得最后一个元素
            var len = dataHolder.data.anim.Count;
            var idx = len - 1;
            var data = dataHolder.data.anim[idx];
            
            //最后一个已经被填充了size值，则创建一个新的AnimStruct
            if (data.size != Vector3Int.zero)
            {
                data = new AnimStruct
                {
                    size = new Vector3Int(x,y,z), voxel = new List<VoxelStruct>(0)
                };
                dataHolder.data.anim.Add(data);
            }
            else
            //未被填充值，则填充之
            {
                data.size = new Vector3Int(x,y,z);
                //设置值
                dataHolder.data.anim[idx] = data; 
            }
            
            //JS原文
//            var x = dataHolder.parseInt32();
//            var y = dataHolder.parseInt32();
//            var z = dataHolder.parseInt32();
//            debugLog("  bounding box size = " + x + ", " + y + ", " + z);
//
//            var data = dataHolder.data.anim[dataHolder.data.anim.length - 1];
//            if (data.size) {
//                data = { size: null, voxels: [] };
//                dataHolder.data.anim.push(data);
//            }
//            data.size = {
//                x: x,
//                y: y,
//                z: z,
//            };
        }

        private static void ContentsOfVoxelChunk(TDataHolder dataHolder)
        {
            var num = dataHolder.ParseInt32();
            Debug.Log("  voxel size = " + num);

            //取得最后一个元素
            var len = dataHolder.data.anim.Count;
            var idx = len - 1;
            var data = dataHolder.data.anim[idx];

            //最后一个元素已被填充了值，则插入一个新的到后面
            if (data.voxel.Count > 0)
            {
                data = new AnimStruct
                {
                    size = Vector3Int.zero, voxel = new List<VoxelStruct>(num)
                };
                for (var i = 0; i < num; i++)
                {
                    data.voxel.Add(new VoxelStruct
                    {
                        x = dataHolder.Next(),
                        y = dataHolder.Next(),
                        z = dataHolder.Next(),
                        colorIndex = dataHolder.Next()
                    });
                }
                
                //添加新值
                dataHolder.data.anim.Add(data);
            }
            else
            {
                for (var i = 0; i < num; i++)
                {
                    data.voxel.Add(new VoxelStruct
                    {
                        x = dataHolder.Next(),
                        y = dataHolder.Next(),
                        z = dataHolder.Next(),
                        colorIndex = dataHolder.Next()
                    });
                }
                
                //更新旧值
                dataHolder.data.anim[idx] = data; 
            }
            
            //JS原文
//            var num = dataHolder.parseInt32();
//            debugLog("  voxel size = " + num);
//
//            var data = dataHolder.data.anim[dataHolder.data.anim.length - 1];
//            if (data.voxels.length) {
//                data = { size: null, voxels: [] };
//                dataHolder.data.anim.push(data);
//            }
//            for (var i = 0; i < num; i++) {
//                data.voxels.push({
//                    x: dataHolder.next(),
//                    y: dataHolder.next(),
//                    z: dataHolder.next(),
//                    colorIndex: dataHolder.next(),
//                });
//            }
        }

        private static void ContentsOfPaletteChunk(TDataHolder dataHolder)
        {
            Debug.Log("  palette");
            for (var i = 0; i < 256; i++)
            {
                var p = new PaletteStruct
                {
                    r = dataHolder.Next(),
                    g = dataHolder.Next(),
                    b = dataHolder.Next(),
                    a = dataHolder.Next()
                };
                
                dataHolder.data.palette.Add(p);
            }
            
            //JS原文
//            debugLog("  palette");
//            for (var i = 0; i < 256; i++) {
//                var p = {
//                    r: dataHolder.next(),
//                    g: dataHolder.next(),
//                    b: dataHolder.next(),
//                    a: dataHolder.next(),
//                };
//                dataHolder.data.palette.push(p);
//            }
        }

        private static void ContentsOfMaterialChunk(TDataHolder dataHolder)
        {
            Debug.Log("  material");

            var id = dataHolder.ParseInt32();
            Debug.Log("    id = " + id);

            var type = dataHolder.ParseInt32();
            Debug.Log("    type = " + type + " (0:diffuse 1:metal 2:glass 3:emissive)");
            
            var weight = dataHolder.ParseInt32();
            Debug.Log("    weight = " + weight);
            
            var propertyBits = dataHolder.ParseInt32();
            Debug.Log("    property bits = " + propertyBits.ToString("D2"));
            var plastic = Convert.ToBoolean(propertyBits & 1);
            var roughness = Convert.ToBoolean(propertyBits & 2);
            var specular = Convert.ToBoolean(propertyBits & 4);
            var ior = Convert.ToBoolean(propertyBits & 8);
            var attenuation = Convert.ToBoolean(propertyBits & 16);
            var power = Convert.ToBoolean(propertyBits & 32);
            var glow = Convert.ToBoolean(propertyBits & 64);
            var isTotalPower = Convert.ToBoolean(propertyBits & 128);
            Debug.Log("      Plastic = " + plastic);
            Debug.Log("      Roughness = " + roughness);
            Debug.Log("      Specular = " + specular);
            Debug.Log("      IOR = " + ior);
            Debug.Log("      Attenuation = " + attenuation);
            Debug.Log("      Power = " + power);
            Debug.Log("      Glow = " + glow);
            Debug.Log("      isTotalPower = " + isTotalPower);

            var valueNum = 0;
            if (plastic) valueNum += 1;
            if (roughness) valueNum += 1;
            if (specular) valueNum += 1;
            if (ior) valueNum += 1;
            if (attenuation) valueNum += 1;
            if (power) valueNum += 1;
            if (glow) valueNum += 1;
            // isTotalPower is no value

            for (var j = 0; j < valueNum; j++) {
              var values = dataHolder.ParseInt32();
              Debug.Log("    normalized property value = " + values);
            }

            //JS原文
//            debugLog("  material");
//          var id = dataHolder.parseInt32();
//          debugLog("    id = " + id);
//  
//          var type = dataHolder.parseInt32();
//          debugLog("    type = " + type + " (0:diffuse 1:metal 2:glass 3:emissive)");
//  
//          var weight = dataHolder.parseInt32();
//          debugLog("    weight = " + logFloat(weight));
//  
//          var propertyBits = dataHolder.parseInt32();
//          debugLog("    property bits = " + propertyBits.toString(2));
//          var plastic = !!(propertyBits & 1);
//          var roughness = !!(propertyBits & 2);
//          var specular = !!(propertyBits & 4);
//          var ior = !!(propertyBits & 8);
//          var attenuation = !!(propertyBits & 16);
//          var power = !!(propertyBits & 32);
//          var glow = !!(propertyBits & 64);
//          var isTotalPower = !!(propertyBits & 128);
//          debugLog("      Plastic = " + plastic);
//          debugLog("      Roughness = " + roughness);
//          debugLog("      Specular = " + specular);
//          debugLog("      IOR = " + ior);
//          debugLog("      Attenuation = " + attenuation);
//          debugLog("      Power = " + power);
//          debugLog("      Glow = " + glow);
//          debugLog("      isTotalPower = " + isTotalPower);
//  
//          var valueNum = 0;
//          if (plastic) valueNum += 1;
//          if (roughness) valueNum += 1;
//          if (specular) valueNum += 1;
//          if (ior) valueNum += 1;
//          if (attenuation) valueNum += 1;
//          if (power) valueNum += 1;
//          if (glow) valueNum += 1;
//          // isTotalPower is no value
//          
//          var values = [];
//          for (var j = 0; j < valueNum; j++) {
//              values[j] = dataHolder.parseInt32();
//              debugLog("    normalized property value = " + logFloat(values[j]));
//          }
        }

        private static void ContentsOfTransformNodeChunk(TDataHolder dataHolder)
        {
            var nodeId = dataHolder.ParseInt32();
            var nodeAttributes = dataHolder.ParseDict();
            var childNodeId = dataHolder.ParseInt32();
            var reservedId = dataHolder.ParseInt32();
            var layerId = dataHolder.ParseInt32();
            var numOfFrames = dataHolder.ParseInt32();
            //todo:真不知道这玩意儿在局部变量里存一下有什么作用
//            var frameAttributes = new  Dictionary<string,string>()[numOfFrames];
            for (var i = 0; i < numOfFrames; i++) {
                dataHolder.ParseDict();
            }
            
//            Debug.Log("  frameAttributes" + frameAttributes);
            Debug.Log("  nodeId = " + nodeId);
            Debug.Log("  nodeAttributes" + nodeAttributes);
            Debug.Log("  childNodeId = " + childNodeId);
            Debug.Log("  reservedId = " + reservedId);
            Debug.Log("  layerId = " + layerId);
            Debug.Log("  numOfFrames = " + numOfFrames);
            
            //JS原文
//            var nodeId = dataHolder.parseInt32();
//            var nodeAttributes = dataHolder.parseDict();
//            var childNodeId = dataHolder.parseInt32();
//            var reservedId = dataHolder.parseInt32();
//            var layerId = dataHolder.parseInt32();
//            var numOfFrames = dataHolder.parseInt32();
//            var frameAttributes = [];
//            for (var i = 0; i < numOfFrames; i++) {
//                frameAttributes[i] = dataHolder.parseDict();
//            }
//
//            debugLog("  nodeId = " + nodeId);
//            debugLog("  nodeAttributes", nodeAttributes);
//            debugLog("  childNodeId = " + childNodeId);
//            debugLog("  reservedId = " + reservedId);
//            debugLog("  layerId = " + layerId);
//            debugLog("  numOfFrames = " + numOfFrames);
//            debugLog("  frameAttributes", frameAttributes);
        }

        private static void ContentsOfGroupNodeChunk(TDataHolder dataHolder)
        {
            var nodeId = dataHolder.ParseInt32();
            var nodeAttributes = dataHolder.ParseDict();
            var numOfChildren = dataHolder.ParseInt32();
            //todo:真不知道这玩意儿在局部变量里存一下有什么作用
//            var childNodeIds = [];
            for (var i = 0; i < numOfChildren; i++) {
                dataHolder.ParseInt32();
            }
        
//            Debug.Log("  childNodeIds " + childNodeIds);

            Debug.Log("  nodeId = " + nodeId);
            Debug.Log("  nodeAttributes" + nodeAttributes);
            Debug.Log("  numOfChildren = " + numOfChildren);
            
            //JS原文
//            var nodeId = dataHolder.parseInt32();
//            var nodeAttributes = dataHolder.parseDict();
//            var numOfChildren = dataHolder.parseInt32();
//            var childNodeIds = [];
//            for (var i = 0; i < numOfChildren; i++) {
//                childNodeIds[i] = dataHolder.parseInt32();
//            }
//            
//            debugLog("  nodeId = " + nodeId);
//            debugLog("  nodeAttributes", nodeAttributes);
//            debugLog("  numOfChildren = " + numOfChildren);
//            debugLog("  childNodeIds", childNodeIds);
        }

        private static void ContentsOfShapeNodeChunk(TDataHolder dataHolder)
        {
            var nodeId = dataHolder.ParseInt32();
            var nodeAttributes = dataHolder.ParseDict();
            var numOfModels = dataHolder.ParseInt32();
//            var modelIds = [];
//            var modelAttributes = [];
            for (var i = 0; i < numOfModels; i++) {
                dataHolder.ParseInt32();
                dataHolder.ParseDict();
            }

//            debugLog("  modelIds", modelIds);
//            debugLog("  modelAttributes", modelAttributes);
            Debug.Log("  nodeId = " + nodeId);
            Debug.Log("  nodeAttributes" + nodeAttributes);
            Debug.Log("  numOfModels = " + numOfModels);
            
            //JS原文
//            var nodeId = dataHolder.parseInt32();
//            var nodeAttributes = dataHolder.parseDict();
//            var numOfModels = dataHolder.parseInt32();
//            var modelIds = [];
//            var modelAttributes = [];
//            for (var i = 0; i < numOfModels; i++) {
//                modelIds[i] = dataHolder.parseInt32();
//                modelAttributes[i] = dataHolder.parseDict();
//            }
//
//            debugLog("  nodeId = " + nodeId);
//            debugLog("  nodeAttributes", nodeAttributes);
//            debugLog("  numOfModels = " + numOfModels);
//            debugLog("  modelIds", modelIds);
//            debugLog("  modelAttributes", modelAttributes);
        }

        private static void ContentsOfMaterialExChunk(TDataHolder dataHolder)
        {
            var materialId = dataHolder.ParseInt32();
            var properties = dataHolder.ParseDict();

            Debug.Log("  materialId = " + materialId);
            Debug.Log("  properties" + properties);
            //JS原文
//        var materialId = dataHolder.parseInt32();
//        var properties = dataHolder.parseDict();
//
//        debugLog("  materialId = " + materialId);
//        debugLog("  properties", properties);
        }
        
    }
}